/* MIT License
 *
 * Copyright (c) 2012 Reuben Smith, Miami University
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.droidlike.vsig;


import com.jme3.input.KeyInput;
import com.jme3.math.FastMath;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
 * Profile.
 * 
 * @author smithre5
 */
class Profile
{   
    /**
     * Creates a profile object and its associated files and directories.
     * 
     * @param name the desired profile name
     * @return a Profile instance if successful; null otherwise
     */
    public static Profile create(String name)
    {
        if (Util.isBlank(name)) {
            return null;
        }
        
        if (!createProfileDirectories(name)) {
            return null;
        }
        
        Profile profile = new Profile(getProfilesPath() + OS_PSEP + name, getDefaultProperties());
        profile.setValue("profile.name", name);
        
        return profile;
    }
    
    
    /**
     * @return the default profile
     */
    public static Profile getDefault()
    {
        return DEFAULT_PROFILE;
    }
    
    
    /**
     * @return an array of available profile names
     */
    public static String[] getProfileNames()
    {
        File profilesDir = new File(getProfilesPath());
        File[] profiles = profilesDir.listFiles(new FileFilter() {
            public boolean accept(File file)
            {
                return file.isDirectory() && file.canWrite();
            }
        });
        
        String[] profileNames = new String[profiles.length];
        for (int i = profileNames.length; --i >= 0;) {
            profileNames[i] = profiles[i].getName();
        }
        
        return profileNames;
    }
    
    
    /**
     * Returns a profile object with the given profile's settings.
     * 
     * @param name the desired profile's name
     * @return a Profile instance if successful; false otherwise
     */
    public static Profile load(String name)
    {
        if (Util.isBlank(name)) {
            return null;
        }
        
        File profileDir = new File(getProfilesPath() + OS_PSEP + name);
        if (!profileDir.exists()) {
            return null;
        }

        Properties profileProps = new Properties(getDefaultProperties());
        
        try {
            FileInputStream fileIn = new FileInputStream(profileDir + OS_PSEP + PROPERTIES_FILE);
            profileProps.loadFromXML(fileIn);
            fileIn.close();
        }
        catch (Exception ex) {
            Logger.getLogger(Profile.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
        
        return new Profile(profileDir.getPath(), profileProps);
    }
    
    
    /**
     * @return an array of all scripts in the user's library
     */
    public File[] getLibraryFiles()
    {
        return new File(profilePath + OS_PSEP + LIBRARY_DIR).listFiles(Profile.libraryFilter);
    }
    
    
    /**
     * Returns the profile setting's value. If the profile does not have this
     * property, the default value is returned. If there is no default, null
     * is returned.
     * 
     * @param property the property name
     * @return a String representing the setting's value; null if no such
     *         property
     */
    public String getValue(String property)
    {
        // smithre5: Might want to look into breaking this out into get<Type>Value()
        // methods. Or generics. Something more... safe.
        
        if (Util.isBlank(property)) {
            return null;
        }
        
        return properties.getProperty(property, getDefaultProperties().getProperty(property));
    }
    
    
    /**
     * Assigns the property value. 
     * 
     * @param property the property to set
     * @param value the value to be set
     */
    public void setValue(String property, String value)
    {
        // Failsafe to prevent user from modifying default profile.
        if (this == Profile.DEFAULT_PROFILE) {
            return;
        }
        
        // smithre5: Maybe some sort of value type checking for known properties?
        
        if (Util.isBlank(property)) {
            return;
        }
       
        properties.setProperty(property, value);
    }
    
    
    /**
     * Writes the current profile settings to file.
     * 
     * @return true if successful; false otherwise
     */
    public boolean save()
    {
        if (properties.getProperty("profile.name").equals(DEFAULT_NAME)) {
            return false;
        }
        
        try {
            FileOutputStream fileOut = new FileOutputStream(propertiesPath);
            properties.storeToXML(fileOut, "VSIG Profile Properties");
            fileOut.close();
        }
        catch (Exception ex) {
            Logger.getLogger(Profile.class.getName()).log(Level.SEVERE, 
                "Failed to save profile to disk.", ex);
            return false;
        }
        
        return true;
    }
    
    
    /**
     * Creates the given profile's directory and all needed subdirectories. This
     * will try to clean up after itself if unsuccessful.
     * 
     * @param name the given profile's name
     * @return true if successful; false otherwise
     */
    private static boolean createProfileDirectories(String name)
    {
        if (Util.isBlank(name)) {
            return false;
        }
        
        String profilesPath = getProfilesPath();
        if (Util.isBlank(profilesPath)) {
            return false;
        }
        
        File profileDir = acquireDirectory(profilesPath + OS_PSEP + name);
        if (profileDir == null) {
            return false;
        }
        
        File libraryDir = acquireDirectory(profileDir.getPath() + OS_PSEP + LIBRARY_DIR);
        if (libraryDir == null) {
            profileDir.delete();
            return false;
        }
        
        File sessionsDir = acquireDirectory(profileDir.getPath() + OS_PSEP + SESSIONS_DIR);
        if (sessionsDir == null) {
            libraryDir.delete();
            profileDir.delete();
            return false;
        }
        
        return true;
    }
    
    
    /**
     * Returns the path to the best usable profiles directory. It will attempt to
     * create necessary directories if they do not exist.
     * 
     * @return a String path if successful; null otherwise
     */
    private static String getProfilesPath()
    {  
        // The first path must be under the app directory in the user's home 
        // directory. This is the best place to try to store things anyway.
        String[] paths = new String[]{
            APP_HOME_DIR + OS_PSEP + PROFILES_DIR,
            PROFILES_DIR
        };
        
        File profilesDir = null;
        for (int i = 0; i < paths.length; ++i) {
            if (i == 0) {
                File appDir = acquireDirectory(APP_HOME_DIR);
                
                if (appDir == null) {
                    continue;
                }
            }
            
            if ((profilesDir = acquireDirectory(paths[i])) != null) {
                break;
            }
        }
        
        return profilesDir.getPath();
    }
    
    
    /**
     * @return the default properties
     */
    private static Properties getDefaultProperties()
    {
        if (defaultProperties == null) {
            defaultProperties = new Properties();
            
            defaultProperties.setProperty("profile.name",           DEFAULT_NAME);
            defaultProperties.setProperty("camera.deadzone",        Integer.toString((int)Math.sqrt(SimulationCamera.DEADZONE)));
            defaultProperties.setProperty("camera.move_rate",       Float.toString(SimulationCamera.MOVE_RATE));
            defaultProperties.setProperty("camera.view_angle",      Float.toString(SimulationCamera.VIEW_ANGLE));
            defaultProperties.setProperty("camera.view_distance",   Float.toString(SimulationCamera.VIEW_DISTANCE_FAR));
            defaultProperties.setProperty("camera.view_rate_pitch", Float.toString(SimulationCamera.VIEW_RATE_PITCH / FastMath.DEG_TO_RAD));
            defaultProperties.setProperty("camera.view_rate_yaw",   Float.toString(SimulationCamera.VIEW_RATE_YAW / FastMath.DEG_TO_RAD));
            defaultProperties.setProperty("camera.zoom_levels",     Integer.toString(SimulationCamera.ZOOM_LEVELS));
            defaultProperties.setProperty("input.debug",            Integer.toString(KeyInput.KEY_GRAVE));
            defaultProperties.setProperty("input.debug_vehicle",    Integer.toString(KeyInput.KEY_X));
            defaultProperties.setProperty("input.camera_reset",     Integer.toString(KeyInput.KEY_Z));
            defaultProperties.setProperty("input.camera_move",      Integer.toString(KeyInput.KEY_LCONTROL));
            defaultProperties.setProperty("input.camera_view",      Integer.toString(KeyInput.KEY_LSHIFT));
            defaultProperties.setProperty("input.usetool_select",   Integer.toString(KeyInput.KEY_1));
            defaultProperties.setProperty("input.usetool_signal",   Integer.toString(KeyInput.KEY_2));
            defaultProperties.setProperty("input.usetool_sensor",   Integer.toString(KeyInput.KEY_3));
            defaultProperties.setProperty("input.usetool_control",  Integer.toString(KeyInput.KEY_4));
        }
        
        return defaultProperties;
    }
    
    
    /**
     * Returns a directory file object of the given path. If the path refers to
     * a directory that does not exist, it attempts to create it. Only the final
     * element in the path is attempted; it will not create any preceeding the 
     * final directory.
     * 
     * @param path the desired directory's path
     * @return a File object if successful; null otherwise
     */
    private static File acquireDirectory(String path)
    {
        if (Util.isBlank(path)) {
            return null;
        }
        
        File directory = new File(path);
        if (directory != null) {
            if (directory.exists()) {
                if (!directory.canWrite()) {
                    directory.delete();
                    directory = null;
                }
            }
            else if (!directory.mkdir()) {
                directory = null;
            }
        }
        
        return directory;
    }
    
    
    /**
     * Default constructor.
     */
    private Profile(String profilePath, Properties properties)
    {
        this.profilePath = profilePath;
        this.propertiesPath = profilePath + OS_PSEP + PROPERTIES_FILE;
        this.properties = properties;
    }
        
    
    public static final String DEFAULT_NAME = "<Default>";
    private static final String OS_PSEP = System.getProperty("file.separator");
    private static final String OS_HOME = System.getProperty("user.home");
    private static final String APP_HOME_DIR = OS_HOME + OS_PSEP + "." + VSIG.TITLE;
    private static final String PROFILES_DIR = "profiles";
    private static final String LIBRARY_DIR = "library";
    private static final String SESSIONS_DIR = "sessions";
    private static final String PROPERTIES_FILE = "profile.xml";
    private static final Profile DEFAULT_PROFILE = new Profile(null, getDefaultProperties());
    private static Properties defaultProperties;
    private static FileFilter libraryFilter = new FileFilter() {
        public boolean accept(File file) 
        {
            if (file.canRead() && file.getName().endsWith(".v")) {
                return true;
            }
            
            return false;
        }
    };
    
    private String profilePath;
    private String propertiesPath;
    private Properties properties;
}